#ifndef __THREAD_THREAD_H
#define __THREAD_THREAD_H

#include "stdint.h"
#include "memory.h"
#include "list.h"

typedef void thread_func(void*);

/* 进程和线程的状态 用task_status枚举表示 */
enum task_status{
    /* 运行态 */
    TASK_RUNNING,
    /* 就绪态 */
    TASK_READY,
    /* 阻塞态 */
    TASK_BLOCKED,
    /* 等待态 */
    TASK_WAITING,
    /* 挂起态 */
    TASK_HANGING,
    /* 终止态 */
    TASK_DIED
};

/* 被中断打断后入栈顺序
 * 由cpu自己入栈的顺序：
 * SS
 * ESP_OLD
 * EFLAGS
 * CS_OLD
 * EIP_OLD
 * 
 * 软件自己中断入栈代码如下：
 *  %2     此代码压入中断错误码
 *  pushad  入栈顺序EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI
 *  push ds
 *  push gs
 *  push fs
 *  push es
 *  push %1 此代码压出中断号
 *
 * 由于栈是先进后出，所以出栈应该相反顺序
 * 所以我们定义结构体的顺序应该相反
 * 
 */
struct intr_stack{
    uint32_t vec_no;	 // kernel.S 宏VECTOR中push %1压入的中断号
    uint32_t edi;
    uint32_t esi;
    uint32_t ebp;
    uint32_t esp_dummy;	 // 虽然pushad把esp也压入,但esp是不断变化的,所以会被popad忽略
    uint32_t ebx;
    uint32_t edx;
    uint32_t ecx;
    uint32_t eax;
    uint32_t gs;
    uint32_t fs;
    uint32_t es;
    uint32_t ds;
    uint32_t error_code;


    void (*eip)(void);
    uint32_t cs;
    uint32_t eflags;
    void* esp;
    uint32_t ss;
};

/* 线程自己的栈 */
struct thread_stack{
    /* 
     * 此处涉及到ABI规范问题
     * 
     * ABI 是 Application Binary Interface ,即应用程序二进制接口,也许部分同学只
     * 听说过 API, API 是 Application Programminglnterface ,即应用程序可编程接口,不过这是库函数和操作系
     * 统的系统调用之间的接口 。 ABI 与此不同, ABI 规定的是更加底层的一套规则,属于编译方面的约定,比
     * 如参数如何传递,返回值如何存储,系统调用的实现方式,目标文件格式或数据类型等。只要操作系统和
     * 应用程序都遵守同 一套 ABI 规则,编译好的应用程序可以无需修改直接在另一套操作系统上运行
     * 
     * 官方规范 SysV_ABI_386-V4：
     * 位于 lntel386 硬件体系上的所有寄存器都具有全局性,因此在函数调用时,这些寄存器对主
     * 调函数和被调函数都可见。这 5 个寄存器 ebp 、 ebx, edi 、 esi 、和 esp 归主调函数所用,其余的寄存器归被调
     * 函数所用 。 换句话说,不管被调函数中是否使用了这 5 个寄存器,在被调函数执行完后,这 5 个寄存器的值不
     * 该被改变 。 因此被调函数必须为主调函数保护好这 5 个寄存器的值,在被调函数运行完之后,这 5 个寄存器的
     * 值必须和运行前一样,它必须在自己的栈中存储这些寄存器的值。
     * 
     * 因为我们将来会在schdule中调用switch_to，switch_to是一个汇编代码
     * 因为我们要在switch_to中保存好这些寄存器，esp会被调用规范所保护
     * 所以我们这里只需要手动保存四个寄存器就好
     */
    uint32_t ebp;
    uint32_t ebx;
    uint32_t edi;
    uint32_t esi;

    /* 线程第一次运行的时候 eip指向kernel_thread
     * 其他时候 eip指向switch_to的返回地址
     */
    void (*eip)(thread_func* func, void* func_arg);
    void (*unused_ret_addr);
    thread_func* func;
    void* func_arg;
};
/* 进程或线程的控制块 */
struct task_struct{
    /* 各内核线程都用自己的内核栈 */
    uint32_t* self_kstack;
    /* 指示线程状态 */
    enum task_status status;
    /* 线程优先级 线程时间片根据优先级决定 */
    uint8_t priority;
    /* 线程名字 */
    char name[16];
    /* 剩余时间片 */
    uint8_t remainning_ticks;
    /* 线程总共在cpu上运行了多久 */
    uint32_t ticks;
    /* 用于插入就绪链表的节点 */
    struct list_elem general_tag;
    /* 用于插入所有线程链表的节点 */
    struct list_elem all_tag;

    /* 当任务是线程时这里为NULL 当任务是进程时这里指向进程页目录表的虚拟地址 */
    uint32_t* pgdir;
    /* 当任务是进程时才会使用到虚拟地址池 */
    struct virtual_addr_pool userprog_vaddr; 

    /* 用于检查栈是否超出边界 */
    uint32_t magic_stack_limitation;        
};


struct task_struct* thread_start(char* name, int prio, thread_func function, void* func_arg);
void thread_init(void);
void init_thread(struct task_struct* pthread, char* name, int prio);
void thread_create(struct task_struct* pthread, thread_func function, void* func_arg);
void schedule(void);
void thread_block(enum task_status status);
void thread_unblock(struct task_struct* pcb);
struct task_struct* running_task(void);
#endif